﻿Imports System.Runtime.CompilerServices
Imports System.ComponentModel

Public Module ToolStripItemExtensions

    Private Structure tsiImageLocationInfo
        Dim target As ToolStripItem
        Dim imageUrl As String
        Dim result As Byte()

        Sub New(ByVal target As ToolStripItem, ByVal imageUrl As String)
            Me.target = target
            Me.imageUrl = imageUrl
        End Sub
    End Structure

    <Extension()> _
    Public Sub SetImageLocation(ByVal tsi As ToolStripItem, ByVal url As String)

        'validate url structure first
        If Not Uri.IsWellFormedUriString(url, UriKind.Absolute) Then
            tsi.Image = Nothing
            Return
        End If

        'checks the url cache first
        If FileStore.ContainsUrlCache(url) Then

            'if url exists in the cache, then loads from the cache
            Using fs = FileStore.OpenUrlCacheFileStream(url, IO.FileMode.Open)
                tsi.Image = Image.FromStream(fs)
                fs.Close()
            End Using

        Else 'creates a new background thread to download the image
            Dim bw As New BackgroundWorker
            bw.WorkerReportsProgress = False
            bw.WorkerSupportsCancellation = False

            'assign event handlers and start the work
            AddHandler bw.DoWork, AddressOf backgroundWorker_DoWork
            AddHandler bw.RunWorkerCompleted, AddressOf backgroundWorker_RunWorkerCompleted
            bw.RunWorkerAsync(New tsiImageLocationInfo(tsi, url))

        End If

    End Sub

    Private Sub backgroundWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)

        Dim args = DirectCast(e.Argument, tsiImageLocationInfo)
        Dim wc = New Net.WebClient

        Try 'try to download the image
            args.result = wc.DownloadData(args.imageUrl)

        Catch ex As Exception 'return a null result if any error occur
            args.result = Nothing

        End Try

        'finish, clean up and pass on results
        e.Result = args
        wc.Dispose()

    End Sub
    Private Sub backgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)

        'check if there are results
        'then use the downloaded data as the control's image
        If e.Result Is Nothing Then Return

        Dim args = DirectCast(e.Result, tsiImageLocationInfo)
        If args.result Is Nothing Then Return

        'NOTE:Althought the file ends in ".ico" sometimes the content inside is of
        'some other format, we'll have to try out different codecs to see which one
        'is will decode the image data properly

        'try rendering the data as icon
        Dim iconResult = tryCreateIcon(args.result)
        If iconResult IsNot Nothing Then
            args.target.Image = iconResult.ToBitmap()

        Else 'try rendering using the image codec
            Dim imageResult = tryCreateImage(args.result)
            If imageResult IsNot Nothing Then
                args.target.Image = imageResult

            Else 'all else fails, use no image
                args.target.Image = Nothing

            End If
        End If

        'if image downloading and rendering succesful
        'saves the data to the cache
        If args.target.Image IsNot Nothing Then

            'saves downloaded data to the cache
            'use the PNG codec so next time we don't have to figure out
            'wether to use Icon codec or window image codecs again
            Using fs = FileStore.OpenUrlCacheFileStream(args.imageUrl, IO.FileMode.Create)
                args.target.Image.Save(fs, Imaging.ImageFormat.Png)
                fs.Close()
            End Using

        End If

    End Sub

    'helper functions
    Private Function tryCreateIcon(ByVal data As Byte()) As Icon

        Try
            Dim result = New Icon(New IO.MemoryStream(data))
            Return result 'successful

        Catch 'error
            Return Nothing

        End Try

    End Function
    Private Function tryCreateImage(ByVal data As Byte()) As Image

        Try
            Dim result = Image.FromStream(New IO.MemoryStream(data))
            Return result 'success

        Catch 'error
            Return Nothing

        End Try

    End Function

End Module
